//
//  WCNewFileWindowController.m
//  WabbitCode
//
//  Created by William Towe on 1/15/09.
//  Copyright 2009 Revolution Software. All rights reserved.
//

#import "WCNewFileWindowController.h"
#import "WCMacros.h"
#import "WCLinkedView.h"
#import "NSResponder+WCExtensions.h"
#import "WCDocumentController.h"
#import "WCNewFileCategory.h"
#import "WCNewFileGroup.h"
#import "WCNewFileTemplate.h"
#import "WCBadgedIconAndTextFieldCell.h"
#import "WCProject.h"
#import "NSPopUpButton+WCExtensions.h"
#import "WCAppController.h"
#import "WCNewTextFileProcessor.h"
#import "WCTextFile.h"
#import "EncodingManager.h"
#import "NSTreeController+WCExtensions.h"
#import "NSDictionary+WCExtensions.h"


NSString *kWCNewFileTemplateInfoShortDescriptionKey = @"shortDescription";
NSString *kWCNewFileTemplateInfoLongDescriptionKey = @"longDescription";
NSString *kWCNewFileTemplateInfoFileExtensionKey = @"fileExtension";

#define kWCNewFileTemplateInfoName @"templateInfo.plist"
#define kWCNewFileTemplatesDirectoryName @"File Templates"
#define kWCNewFileTemplateMainFileName @"template"

@interface WCNewFileWindowController (Private)
- (void)_setupUserInterface;
- (void)_resetUserInterface;
- (void)_updateFileLocationPopUpButtonWithProject:(WCProject *)project;
- (BOOL)_createNewFileAtPath:(NSString *)filePath error:(NSError **)error;
@end

@implementation WCNewFileWindowController
#pragma mark *** Subclass Overrides
- (id)init {
	if (!(self = [super initWithWindowNibName:@"WCNewFileWindow"]))
		return nil;
	
	_newFileCategories = [[NSMutableArray alloc] init];
	
	return self;
}

- (void)dealloc {
	[_newFileName release];
	[_newFileCategories release];
	[super dealloc];
}

- (void)windowWillLoad {
	[super windowWillLoad];
	
	WCNewFileCategory *builtInCategory = [WCNewFileCategory newFileCategoryWithName:NSLocalizedString(@"Categories",@"Categories")];
	NSString *fileTemplatesPath = [[NSBundle mainBundle] pathForResource:kWCNewFileTemplatesDirectoryName ofType:@""];
	NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
	NSString *userFileTemplatesPath = nil;
	
	if ([searchPaths count])
		userFileTemplatesPath = [[[searchPaths objectAtIndex:0] stringByAppendingPathComponent:[[NSProcessInfo processInfo] processName]] stringByAppendingPathComponent:kWCNewFileTemplatesDirectoryName];
	
	for (NSString *groupName in [[NSFileManager defaultManager] directoryContentsAtPath:fileTemplatesPath]) {
		WCNewFileGroup *group = [WCNewFileGroup newFileGroupWithName:groupName];
		NSString *groupPath = [fileTemplatesPath stringByAppendingPathComponent:groupName];
		BOOL isDirectory = NO;
		
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		// create the same group in the user file templates folder if it doesn't already exist
		if (userFileTemplatesPath) {
			NSString *userFileTemplatesGroupPath = [userFileTemplatesPath stringByAppendingPathComponent:groupName];
			if (![[NSFileManager defaultManager] fileExistsAtPath:userFileTemplatesGroupPath isDirectory:&isDirectory] || !isDirectory) {
				NSError *error = nil;
				if (![[NSFileManager defaultManager] createDirectoryAtPath:userFileTemplatesGroupPath withIntermediateDirectories:YES attributes:nil error:&error]) {
					// TODO: display the error?
				}
			}
		}
		
		for (NSString *templateName in [[NSFileManager defaultManager] directoryContentsAtPath:groupPath]) {
			NSString *templatePath = [groupPath stringByAppendingPathComponent:templateName];
			
			if (![[NSFileManager defaultManager] fileExistsAtPath:templatePath isDirectory:&isDirectory] || !isDirectory)
				continue;
			
			NSString *templateInfoPath = [templatePath stringByAppendingPathComponent:kWCNewFileTemplateInfoName];
			
			if (![[NSFileManager defaultManager] fileExistsAtPath:templateInfoPath])
				continue;
			
			NSDictionary *templateInfo = [NSDictionary dictionaryWithContentsOfFile:templateInfoPath];
			
			if (![templateInfo isValidNewFileTemplateInfoPlist])
				continue;
			
			WCNewFileTemplate *template = [WCNewFileTemplate newFileTemplateWithName:templateName templateInfo:templateInfo templateDirectory:templatePath];
			
			[[group mutableChildNodes] addObject:template];
		}
		[pool release];
		
		if ([[group childNodes] count])
			[[builtInCategory mutableChildNodes] addObject:group];
	}
	
	if ([[builtInCategory childNodes] count])
		[[self newFileCategories] addObject:builtInCategory];
	
	// load the user file templates
	if (!userFileTemplatesPath)
		return;
	
	WCNewFileCategory *userCategory = [WCNewFileCategory newFileCategoryWithName:NSLocalizedString(@"User Categories",@"User Categories")];
	
	for (NSString *groupName in [[NSFileManager defaultManager] directoryContentsAtPath:userFileTemplatesPath]) {
		BOOL usingAnExistingGroup = NO;
		WCNewFileGroup *group = nil;
		// check the default group names
		for (WCNewFileGroup *existingGroup in [builtInCategory childNodes]) {
			if ([groupName isEqualToString:[existingGroup name]]) {
				group = existingGroup;
				usingAnExistingGroup = YES;
				break;
			}
		}
		if (!group)
			group = [WCNewFileGroup newFileGroupWithName:groupName];
		NSString *groupPath = [userFileTemplatesPath stringByAppendingPathComponent:groupName];
		
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
		for (NSString *templateName in [[NSFileManager defaultManager] directoryContentsAtPath:groupPath]) {
			NSString *templatePath = [groupPath stringByAppendingPathComponent:templateName];
			BOOL isDirectory = NO;
			
			if (![[NSFileManager defaultManager] fileExistsAtPath:templatePath isDirectory:&isDirectory] || !isDirectory)
				continue;
			
			NSString *templateInfoPath = [templatePath stringByAppendingPathComponent:kWCNewFileTemplateInfoName];
			
			if (![[NSFileManager defaultManager] fileExistsAtPath:templateInfoPath])
				continue;
			
			NSDictionary *templateInfo = [NSDictionary dictionaryWithContentsOfFile:templateInfoPath];
			
			if (![templateInfo isValidNewFileTemplateInfoPlist])
				continue;
			
			WCNewFileTemplate *template = [WCNewFileTemplate newFileTemplateWithName:templateName templateInfo:templateInfo templateDirectory:templatePath];
			
			[[group mutableChildNodes] addObject:template];
		}
		[pool release];
		
		if (!usingAnExistingGroup && [[group childNodes] count])
			[[userCategory mutableChildNodes] addObject:group];
	}
	
	if ([[userCategory childNodes] count])
		[[self newFileCategories] addObject:userCategory];
}

- (void)windowDidLoad {
	[super windowDidLoad];
	
	[[self window] setContentBorderThickness:32.0 forEdge:NSMinYEdge];
	
	[[self window] setTitle:NSLocalizedString(@"New File",@"new file window title")];
	
	[[self currentView] setFrameSize:[[self swapView] frame].size];
	[[self swapView] addSubview:[self currentView]];
	
	[[_outlineView dataSource] setSelectionIndexPath:[[NSIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]];
}
#pragma mark NSSplitView Delegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)offset {
	if ([splitView isVertical])
		return proposedMin + 150.0;
	else if (![splitView isVertical])
		return proposedMin + 100.0;
	return proposedMin;
}

- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)offset {
	if ([splitView isVertical])
		return proposedMax - 250.0;
	else if (![splitView isVertical])
		return proposedMax - 125.0;
	return proposedMax;
}

- (NSRect)splitView:(NSSplitView *)splitView additionalEffectiveRectOfDividerAtIndex:(NSInteger)dividerIndex {
	if ([splitView isVertical]) {
		return NSMakeRect(NSMaxX([_gradientBarView bounds]) - [_leftSplitterHandleImageView frame].size.width, [splitView frame].size.height - [_gradientBarView frame].origin.y - [_gradientBarView bounds].size.height, [_leftSplitterHandleImageView frame].size.width, [_gradientBarView bounds].size.height);
	}
	return NSZeroRect;
}

- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize {
	if ([splitView inLiveResize]) {
		// keep the project outline view and symbols table view the same size while the resizing the middle view
		if ([splitView isVertical]) {
			NSArray *subviews = [splitView subviews];
			NSView *leftView = [subviews objectAtIndex:0];
			NSView *rightView = [subviews lastObject];
			NSRect currentFrame = [splitView frame];
			NSRect leftFrame = [leftView frame];
			NSRect rightFrame = [rightView frame];
			CGFloat dividerThickness = [splitView dividerThickness];
			
			leftFrame.size.height = currentFrame.size.height;
			rightFrame.size.width = currentFrame.size.width - leftFrame.size.width - dividerThickness;
			rightFrame.size.height = currentFrame.size.height;
			rightFrame.origin.x = leftFrame.size.width + dividerThickness;
			
			[leftView setFrame:leftFrame];
			[rightView setFrame:rightFrame];
		}
		// keep the top view in the middle split view the same while resizing the bottom view
		else {
			NSArray *subviews = [splitView subviews];
			NSView *topView = [subviews objectAtIndex:0];
			NSView *bottomView = [subviews lastObject];
			NSRect currentFrame = [splitView frame];
			NSRect topFrame = [topView frame];
			NSRect bottomFrame = [bottomView frame];
			CGFloat dividerThickness = [splitView dividerThickness];
			
			bottomFrame.size.width = currentFrame.size.width;
			topFrame.size.height = currentFrame.size.height - bottomFrame.size.height - dividerThickness;
			topFrame.size.width = currentFrame.size.width;
			bottomFrame.origin.y = topFrame.size.height + dividerThickness;
			
			[topView setFrame:topFrame];
			[bottomView setFrame:bottomFrame];
		}
	}
	else {
		[splitView adjustSubviews];
	}
}
#pragma mark NSOutlineView Delegate
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item {
	if ([[item representedObject] isKindOfClass:[WCNewFileGroup class]]) {
		[cell setBadgeCount:[[[item representedObject] childNodes] count]];
	}
	else {
		[cell setBadgeCount:0];
	}
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
	return (![[item representedObject] isLeaf]);
}

- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item {
	return [[item representedObject] isLeaf];
}

#pragma mark IBActions
- (IBAction)approveModalWindowAction:(id)sender; {
	if (sender == _collectionView) {
		[self nextAction:nil];
		return;
	}
	
	WCNewFileTemplate *selectedTemplate = [self selectedNewFileTemplate];
	NSString *directoryPath = [self newFileDirectoryPath];
	NSString *fileName = [[self newFileName] stringByDeletingPathExtension];
	NSString *fileExtension = ([[[self newFileName] pathExtension] length])?[[self newFileName] pathExtension]:[selectedTemplate fileExtension];
	NSString *filePath = [directoryPath stringByAppendingPathComponent:[fileName stringByAppendingPathExtension:fileExtension]];
	
	// check to see if the file already exists in the chosen directory
	if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
		NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\"%@\" Already Exists",@"new file window file already exists alert title"), [fileName stringByAppendingPathExtension:fileExtension]];
		NSString *format = [NSString stringWithFormat:NSLocalizedString(@"A file named \"%@\" already exists at \"%@\". Would you like to add a numbered suffix to the file name and create a new file with that name?",@"new file window new file already exists format string"), [fileName stringByAppendingPathExtension:fileExtension], filePath];
		NSAlert *alert = [NSAlert alertWithMessageText:message defaultButton:NSLocalizedString(@"Add Suffix",@"Add Suffix") alternateButton:NSLocalizedButtonCancel otherButton:nil informativeTextWithFormat:format];
		[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(_alertDidEnd:code:info:) contextInfo:(void *)[filePath retain]];
		return;
	}
	
	NSError *error = nil;
	if (![self _createNewFileAtPath:filePath error:&error]) {
		// TODO: display an alert
		return;
	}
	
	[self _resetUserInterface];
	[super approveModalWindowAction:nil];
}

- (IBAction)dismissModalWindowAction:(id)sender; {
	[self _resetUserInterface];
	[super dismissModalWindowAction:nil];
}
#pragma mark *** Public Methods ***
+ (WCNewFileWindowController *)sharedInstance; {
	static id _sharedInstance = nil;
	if (!_sharedInstance) {
		_sharedInstance = [[[self class] alloc] init];
	}
	return _sharedInstance;
}

- (void)presentNewFileWindow; {
	if (!_hasPerformedSetupForTheFirstTime) {
		_hasPerformedSetupForTheFirstTime = YES;
		// force the window to load so we can set up everything
		(void)[self window];
	}
	
	[self _setupUserInterface];
	
	NSInteger result = [NSApp runModalForWindow:[self window]];
	
	if (result != NSOKButton)
		return;
}
#pragma mark Accessors
@synthesize swapView=_swapView,newFileName=_newFileName,newFileCategories=_newFileCategories;

@dynamic newFileDirectoryPath;
- (NSString *)newFileDirectoryPath {
	return [[_fileLocationPopUpButton selectedItem] representedObject];
}

@dynamic selectedProject;
- (WCProject *)selectedProject {
	return [[_addToProjectPopUpButton selectedItem] representedObject];
}

@dynamic selectedNewFileTemplate;
- (WCNewFileTemplate *)selectedNewFileTemplate {
	return [[_arrayController selectedObjects] lastObject];
}

@dynamic currentView;
- (WCLinkedView *)currentView {
	return _currentView;
}
- (void)setCurrentView:(WCLinkedView *)value {
	if (_currentView == value)
		return;
	
	[value setFrameSize:[[self swapView] frame].size];
	[[self swapView] replaceSubview:_currentView with:value];
	_currentView = value;
	
	[[self window] makeFirstResponder:[_currentView firstResponder]];
}

- (BOOL)validateNewFileName:(id *)value error:(NSError **)error {
	return YES;
}
#pragma mark IBActions
- (IBAction)nextAction:(id)sender; {
	WCLinkedView *nextView = [[self currentView] nextView];
	
	if (!nextView) {
		[self approveModalWindowAction:nil];
		return;
	}
	
	[self setCurrentView:nextView];
}

- (IBAction)previousAction:(id)sender; {
	[self setCurrentView:[[self currentView] previousView]];
}

- (IBAction)addToProjectPopUpButtonAction:(id)sender; {
	[self _updateFileLocationPopUpButtonWithProject:[self selectedProject]];	
}
#pragma mark *** Private Methods ***
- (void)_setupUserInterface; {	
	// set the initial new file name
	[self setNewFileName:NSLocalizedString(@"untitled",@"untitled")];
	
	// add the current list of projects
	for (WCProject *project in [[WCDocumentController sharedDocumentController] allProjects]) {
		NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:[project projectName] action:[_addToProjectPopUpButton action] keyEquivalent:@""] autorelease];
		[item setRepresentedObject:project];
		[[_addToProjectPopUpButton menu] addItem:item];
	}
	
	WCProject *currentProject = [[WCDocumentController sharedDocumentController] currentProject];
	
	// select the current project in the list
	[_addToProjectPopUpButton selectItemWithRepresentedObject:currentProject];
	
	[self _updateFileLocationPopUpButtonWithProject:currentProject];
	
	WCGroup *group = [currentProject groupForNewFileInsertionAndInsertionIndex:NULL];
	
	(void)[_fileLocationPopUpButton selectItemWithRepresentedObject:[group absoluteFilePath]];
}

- (void)_resetUserInterface; {
	[self setCurrentView:_firstView];
	[_addToProjectPopUpButton removeAllItems];
	[_fileLocationPopUpButton removeAllItems];
}

- (void)_updateFileLocationPopUpButtonWithProject:(WCProject *)project; {
	WCProject *currentProject = project;
	
	[_fileLocationPopUpButton removeAllItems];
	
	// populate the location pop up menu with the folders of the current project
	NSString *projectFolderPath = [currentProject projectFolderPath];
	NSMenuItem *rootItem = [[[NSMenuItem alloc] initWithTitle:[projectFolderPath lastPathComponent] action:[_fileLocationPopUpButton action] keyEquivalent:@""] autorelease];
	[rootItem setToolTip:projectFolderPath];
	[rootItem setImage:[NSImage imageNamed:@"Folder16x16"]];
	[rootItem setRepresentedObject:projectFolderPath];
	[[_fileLocationPopUpButton menu] addItem:rootItem];
	
	BOOL isDirectory = NO;
	for (NSString *relativePath in [[NSFileManager defaultManager] enumeratorAtPath:projectFolderPath]) {
		NSString *filePath = [projectFolderPath stringByAppendingPathComponent:relativePath];
		if (![[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory] || !isDirectory || [[NSWorkspace sharedWorkspace] isFilePackageAtPath:filePath] || [relativePath hasPrefix:@"."])
			continue;
		
		NSUInteger indentationLevel = [[relativePath pathComponents] count];
		NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:[filePath lastPathComponent] action:[_fileLocationPopUpButton action] keyEquivalent:@""] autorelease];
		[item setToolTip:filePath];
		[item setImage:[NSImage imageNamed:@"Folder16x16"]];
		[item setIndentationLevel:indentationLevel];
		[item setRepresentedObject:filePath];
		[[_fileLocationPopUpButton menu] addItem:item];
	}
}

- (BOOL)_createNewFileAtPath:(NSString *)filePath error:(NSError **)error; {
	WCNewFileTemplate *template = [self selectedNewFileTemplate];
	NSString *templateExtension = [template fileExtension];
	NSString *templatePath = [template templateDirectory];
	NSString *templateFilePath = [templatePath stringByAppendingPathComponent:[kWCNewFileTemplateMainFileName stringByAppendingPathExtension:templateExtension]];
	
	NSData *newFileData = nil;
	
	// we are creating a file with the contents of the template file
	if ([[NSFileManager defaultManager] fileExistsAtPath:templateFilePath]) {
		// TODO: handle creating a new file from a template file
		newFileData = [[WCNewTextFileProcessor sharedInstance] dataByReplacingPlaceholdersInString:[NSString stringWithContentsOfFile:templateFilePath encoding:NSUTF8StringEncoding error:NULL] usingEncoding:NSUTF8StringEncoding withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[[self selectedProject] projectName], WCNewTextFileProcessorProjectNameKey, [filePath lastPathComponent], WCNewTextFileProcessorFileNameKey, nil]];
	}	
	// we are creating an empty file
	else {
		NSString *newFileHeader = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"default" ofType:@"txt"] encoding:NSUTF8StringEncoding error:NULL];
		newFileData = [[WCNewTextFileProcessor sharedInstance] dataByReplacingPlaceholdersInString:newFileHeader usingEncoding:NSUTF8StringEncoding withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[[self selectedProject] projectName], WCNewTextFileProcessorProjectNameKey, [filePath lastPathComponent], WCNewTextFileProcessorFileNameKey, nil]];
	}
	
	if (![[NSFileManager defaultManager] createFileAtPath:filePath contents:newFileData attributes:nil]) {
		// TODO: construct an error
		return NO;
	}
	
	id newFile = nil;
	if ([kWCDocumentFileExtensions containsObject:[filePath pathExtension]]) {
		NSStringEncoding encoding = NSUserDefaultsUnsignedIntegerForKey(kWCPrefsEditorDefaultFileEncodingKey);
		if (encoding == NoStringEncoding)
			encoding = kWCDefaultFileEncoding;
		
		newFile = [WCTextFile textFileWithFilePath:filePath fileEncoding:encoding inProject:[self selectedProject]];
	}
	else {
		newFile = [WCFile fileWithFilePath:filePath inProject:[self selectedProject]];
	}
	
	NSUInteger index = 0;
	WCGroup *group = [[self selectedProject] groupForNewFileInsertionAndInsertionIndex:&index];
	
	[newFile setProject:[self selectedProject]];
	[[group mutableChildNodes] insertObject:newFile atIndex:index];
	
	[[[self selectedProject] projectTreeController] selectRepresentedObject:newFile];
	
	if (NSUserDefaultsBoolForKey(kWCPrefsNewFileOpenNewFileAfterCreationKey) &&
		[newFile isKindOfClass:[WCTextFile class]]) {
		
		[[self selectedProject] addTabViewItemForTextFile:newFile inTabViewContext:[self selectedProject]];
	}
	
	return YES;
}
#pragma mark Callbacks
- (void)_alertDidEnd:(NSAlert *)alert code:(NSInteger)code info:(void *)info {
	[(id)info autorelease];
	if (code != NSAlertDefaultReturn)
		return;
	
	NSString *fileName = [[(NSString *)info lastPathComponent] stringByDeletingPathExtension];
	NSString *fileExtension = [(NSString *)info pathExtension];
	NSString *directoryPath = [(NSString *)info stringByDeletingLastPathComponent];
	NSUInteger fileSuffix = 1;
	while ([[NSFileManager defaultManager] fileExistsAtPath:[directoryPath stringByAppendingPathComponent:[[NSString stringWithFormat:@"%@%u", fileName, fileSuffix] stringByAppendingPathExtension:fileExtension]]])
		fileSuffix++;
	
	NSError *error = nil;
	if (![self _createNewFileAtPath:[[NSString stringWithFormat:@"%@%u", fileName, fileSuffix] stringByAppendingPathExtension:fileExtension] error:&error]) {
		// TODO: display an alert
		
		return;
	}
	
	[self _resetUserInterface];
	[super approveModalWindowAction:nil];
}
@end
